package me.leon.ext.android

import android.Manifest.permission
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.net.wifi.WifiManager
import android.os.Build
import android.provider.Settings
import android.telephony.TelephonyManager
import androidx.annotation.RequiresPermission
import java.io.File
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.SocketException
import java.util.*
import me.leon.ext.Utils.app
import me.leon.ext.android.ShellUtils.execCmd

/**
 * <pre> author: Blankj blog : http://blankj.com time : 2016/8/1 desc : utils about device </pre> *
 */
object DeviceUtils {

    /** whether device is rooted. */
    fun isDeviceRooted() =
        arrayOf(
            "/system/bin/",
            "/system/xbin/",
            "/sbin/",
            "/system/sd/xbin/",
            "/system/bin/failsafe/",
            "/data/local/xbin/",
            "/data/local/bin/",
            "/data/local/",
            "/system/sbin/",
            "/usr/bin/",
            "/vendor/bin/"
        )
            .map { File("${it}su").exists() }
            .firstOrNull { it }
            ?: false

    /** whether ADB is enabled. */
    val adbEnabled: Boolean
        get() = Settings.Secure.getInt(app.contentResolver, Settings.Global.ADB_ENABLED, 0) > 0

    /** the version name of device's system. */
    val sdkVersionName: String
        get() = Build.VERSION.RELEASE

    /** the version code of device's system. */
    val sdkVersionCode: Int
        get() = Build.VERSION.SDK_INT

    /**
     * the android id of device.
     *
     * api >= 26 The value may change if a factory reset is performed on the device or if an APK
     * signing key changes.
     *
     * api <26 generated when the user first sets up the device and should remain constant for the
     * lifetime of the user's device.
     */
    val androidID: String
        get() =
            Settings.Secure.getString(app.contentResolver, Settings.Secure.ANDROID_ID)?.takeIf {
                it != "9774d56d682e549c"
            }
                ?: ""
    val wifiEnabled: Boolean
        get() = (app.getSystemService(Context.WIFI_SERVICE) as WifiManager?)?.isWifiEnabled ?: false

    /**
     *
     * Enable or disable wifi. <p>Must hold {@code <uses-permission
     * android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
     *
     * @deprecated Starting with Build.VERSION_CODES#Q, applications are not allowed to
     * enable/disable Wi-Fi. <b>Compatibility Note:</b> For applications targeting {@link
     * android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code false} and
     * will have no effect. If apps are targeting an older SDK ( {@link
     * android.os.Build.VERSION_CODES#P} or below), they can continue to use this API.
     */
    @RequiresPermission(permission.CHANGE_WIFI_STATE)
    fun enableWifi(enabled: Boolean) =
        (app.getSystemService(Context.WIFI_SERVICE) as WifiManager?)?.run {
            isWifiEnabled.takeIf { enabled != it }?.let { isWifiEnabled = enabled }
        }

    val uuid: String
        get() = UUID.randomUUID().toString().replace("-", "")

    val isEmulator: Boolean
        get() {
            val checkProperty =
                Build.FINGERPRINT.startsWith("generic") ||
                    Build.FINGERPRINT.lowercase().contains("vbox") ||
                    Build.FINGERPRINT.lowercase().contains("test-keys") ||
                    Build.MODEL.contains("google_sdk") ||
                    Build.MODEL.contains("Emulator") ||
                    Build.MODEL.contains("Android SDK built for x86") ||
                    Build.MANUFACTURER.contains("Genymotion") ||
                    Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") ||
                    "google_sdk" == Build.PRODUCT
            if (checkProperty) return true
            var operatorName = ""

            (app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).let {
                operatorName = it.networkOperatorName.takeIf { !it.isNullOrBlank() } ?: ""
            }

            if (operatorName.lowercase() == "android") return true

            val url = "tel:123456"
            val intent = Intent(Intent.ACTION_DIAL).apply { data = Uri.parse(url) }

            return intent.resolveActivity(app.packageManager) == null
        }

    val isTablet: Boolean
        get() =
            (app.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >=
                Configuration.SCREENLAYOUT_SIZE_LARGE

    /**
     * Return an ordered list of ABIs supported by this device. The most preferred ABI is the first
     * element in the list.
     */
    val supportABIs: Array<String>
        get() =
            Build.SUPPORTED_ABIS.takeIf { Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP }
                ?: arrayOf(Build.CPU_ABI, Build.CPU_ABI2).takeIf { !Build.CPU_ABI2.isNullOrEmpty() }
                    ?: arrayOf(Build.CPU_ABI)

    /** Return the model of device. */
    val model: String
        get() = Build.MODEL?.trim()?.replace("\\s*", "") ?: ""

    /** Return the manufacturer of the product/hardware. */
    val manufacturer: String
        get() = Build.MANUFACTURER

    /**
     * Return the MAC address.
     *
     * Must hold `<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />`,
     * `<uses-permission android:name="android.permission.INTERNET" />`, `<uses-permission
     * android:name="android.permission.CHANGE_WIFI_STATE" />`
     *
     * @return the MAC address
     */
    @RequiresPermission(
        allOf = [permission.ACCESS_WIFI_STATE, permission.INTERNET, permission.CHANGE_WIFI_STATE]
    )
    fun getMacAddress(): String? {
        val macAddress = getMacAddress(*(null as Array<String>))
        if (macAddress != "" || wifiEnabled) return macAddress
        enableWifi(true)
        enableWifi(false)
        return getMacAddress(*null as Array<String>)
    }

    private fun getMacAddressByFile(): String? {
        var result = execCmd("getprop wifi.interface", false)
        if (result.result == 0) {
            val name = result.successMsg
            if (name != null) {
                result = execCmd("cat /sys/class/net/$name/address", false)
                if (result.result == 0) {
                    val address = result.successMsg
                    if (address != null && address.isNotEmpty()) {
                        return address
                    }
                }
            }
        }
        return "02:00:00:00:00:00"
    }

    private fun getInetAddress(): InetAddress? {
        try {
            val nis = NetworkInterface.getNetworkInterfaces()
            while (nis.hasMoreElements()) {
                val ni = nis.nextElement()
                // To prevent phone of xiaomi return "10.0.2.15"
                if (!ni.isUp) continue
                val addresses = ni.inetAddresses
                while (addresses.hasMoreElements()) {
                    val inetAddress = addresses.nextElement()
                    if (!inetAddress.isLoopbackAddress) {
                        val hostAddress = inetAddress.hostAddress
                        if (hostAddress.indexOf(':') < 0) return inetAddress
                    }
                }
            }
        } catch (e: SocketException) {
            e.printStackTrace()
        }
        return null
    }

    private fun getMacAddressByInetAddress(): String? {
        try {
            val inetAddress = getInetAddress()
            if (inetAddress != null) {
                val ni = NetworkInterface.getByInetAddress(inetAddress)
                if (ni != null) {
                    val macBytes = ni.hardwareAddress
                    if (macBytes != null && macBytes.isNotEmpty()) {
                        val sb = StringBuilder()
                        for (b in macBytes) {
                            sb.append(String.format("%02x:", b))
                        }
                        return sb.substring(0, sb.length - 1)
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return "02:00:00:00:00:00"
    }

    /**
     * Return the MAC address.
     *
     * Must hold `<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />`,
     * `<uses-permission android:name="android.permission.INTERNET" />`
     *
     * @return the MAC address
     */
    @RequiresPermission(allOf = [permission.ACCESS_WIFI_STATE, permission.INTERNET])
    fun getMacAddress(vararg excepts: String): String? {
        var macAddress = getMacAddressByNetworkInterface()
        if (isAddressNotInExcepts(macAddress, *excepts)) {
            return macAddress
        }
        macAddress = getMacAddressByInetAddress()
        if (isAddressNotInExcepts(macAddress, *excepts)) {
            return macAddress
        }
        macAddress = getMacAddressByWifiInfo()
        if (isAddressNotInExcepts(macAddress, *excepts)) {
            return macAddress
        }
        macAddress = getMacAddressByFile()
        return if (isAddressNotInExcepts(macAddress, *excepts)) {
            macAddress
        } else ""
    }

    private fun isAddressNotInExcepts(address: String?, vararg excepts: String?): Boolean {
        if (excepts.isNullOrEmpty()) {
            return "02:00:00:00:00:00" != address
        }
        for (filter in excepts) {
            if (address == filter) {
                return false
            }
        }
        return true
    }

    @SuppressLint("MissingPermission", "HardwareIds")
    private fun getMacAddressByWifiInfo(): String? {
        try {
            val wifi = app.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager?
            if (wifi != null) {
                val info = wifi.connectionInfo
                if (info != null) return info.macAddress
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
        return "02:00:00:00:00:00"
    }

    private fun getMacAddressByNetworkInterface(): String? {
        try {
            val nis = NetworkInterface.getNetworkInterfaces()
            while (nis.hasMoreElements()) {
                val ni = nis.nextElement()
                if (ni == null || !ni.name.equals("wlan0", ignoreCase = true)) continue
                val macBytes = ni.hardwareAddress
                if (macBytes != null && macBytes.isNotEmpty()) {
                    val sb = java.lang.StringBuilder()
                    for (b in macBytes) {
                        sb.append(String.format("%02x:", b))
                    }
                    return sb.substring(0, sb.length - 1)
                }
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
        return "02:00:00:00:00:00"
    }
}
